package com.gitee.wsl.ext.number

import com.gitee.wsl.base.ValueRange
import com.gitee.wsl.math.Math.constrain
import kotlin.jvm.JvmName
import kotlin.math.roundToInt

fun Int.mapRange(fromMin: Int, fromMax: Int, toMin: Int, toMax: Int): Int {
    return ((this - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin)
}

fun Long.mapRange(fromMin: Long, fromMax: Long, toMin: Long, toMax: Long): Long {
    return ((this - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin)
}


/**
 * Re-maps a number from one range to another.
 *
 * @param value  the incoming value to be converted
 * @param start1 lower bound of the value's current range
 * @param stop1  upper bound of the value's current range
 * @param start2 lower bound of the value's target range
 * @param stop2  upper bound of the value's target range
 * @param withinBounds constrain the value to the newly mapped range
 * @return remapped number
 */
fun Float.map(start1: Float, stop1: Float, start2: Float, stop2: Float, withBounds: Boolean = true) = map(start1,stop1,start2,stop2,this,withBounds)

@JvmName("mapFloat")
fun map(start1: Float, stop1: Float, start2: Float, stop2: Float, value:Float,withBounds: Boolean = true): Float {
    val newval = (value - start1) / (stop1 - start1) * (stop2 - start2) + start2
    return if (!withBounds) {
        newval
    } else {
        if (start2 < stop2) {
            constrain(newval, start2, stop2)
        } else {
            constrain(newval, stop2, start2)
        }
    }
}

/**
 * Linearly maps a value, which is given in the before domain to a value in the after domain.
 * @param beforeLeft the lowest value of the before range
 * @param beforeRight the highest value of the before range
 * @param afterLeft the lowest value of the after range
 * @param afterRight the highest value of the after range
 * @param value the value to be mapped
 * @param clamp constrain the result to the after range
 * @return a value in the after range
 */
@JvmName("mapDouble")
fun map(beforeLeft: Double, beforeRight: Double,
        afterLeft: Double, afterRight: Double,
        value: Double,
        clamp: Boolean = false): Double {

    val db = (beforeRight - beforeLeft)
    val da = (afterRight - afterLeft)

    return if (db != 0.0) {
        val n = (value - beforeLeft) / db
        afterLeft + (if (clamp) saturate(n) else n) * da
    } else {
        val n = value - beforeLeft
        afterLeft + (if (clamp) saturate(n) else n) * da
    }
}

/**
 * Linearly maps a value, which is given in the before domain to a value in the after domain.
 * @param before the before range
 * @param after the after range
 * @param value the value to be mapped
 * @param clamp constrain the result to the [after] range
 * @return a value in the [after] range
 */
fun map(
    before: ClosedFloatingPointRange<Double>,
    after: ClosedFloatingPointRange<Double>,
    value: Double,
    clamp: Boolean = false
): Double = map(before.start, before.endInclusive, after.start, after.endInclusive, value, clamp)

/**
 * Linearly maps a value, which is given in the before domain to a value in the after domain
 * @param beforeLeft the lowest value of the before range
 * @param beforeRight the highest value of the before range
 * @param afterLeft the lowest value of the after range
 * @param afterRight the highest value of the after range
 * @param clamp constrain the result to the after range
 * @return a value in the after range
 */
@JvmName("doubleMap")
fun Double.map(beforeLeft: Double, beforeRight: Double,
               afterLeft: Double, afterRight: Double,
               clamp: Boolean = false): Double {
    return map(beforeLeft, beforeRight, afterLeft, afterRight, this, clamp)
}

/**
 * Linearly maps a value, which is given in the before domain to a value in the after domain.
 * @param before the before range
 * @param after the after range
 * @param clamp constrain the result to the [after] range
 * @return a value in the [after] range
 */
fun Double.map(
    before: ClosedFloatingPointRange<Double>,
    after: ClosedFloatingPointRange<Double>,
    clamp: Boolean = false
): Double = map(before, after, this, clamp)

/**
 * Returns this value with the specified [sourceRange] (default: `0.0..1.0`) mapped to the specified [destinationRange].
 *
 * @throws IllegalArgumentException if this value isn't in the specified [sourceRange]
 */
@Suppress("NOTHING_TO_INLINE")
inline fun Double.map(
    destinationRange: ClosedRange<Double>,
    sourceRange: ClosedRange<Double> = ValueRange.Normalized,
): Double {
    require(this in sourceRange) { "$this must be in $sourceRange" }
    val destinationDiff = destinationRange.endInclusive - destinationRange.start
    val sourceDiff = sourceRange.endInclusive - sourceRange.start
    return destinationRange.start + (destinationDiff * (this - sourceRange.start)) / sourceDiff
}


/**
 * Returns this value with the specified [sourceRangeStart] (default: `0.0`) and [sourceRangeEndInclusive] (default: `1.0`)
 * mapped to the [ClosedRange] starting with the specified [destinationRangeStart] (default: `0.0`) and ending with the
 * specified [destinationRangeEndInclusive].
 *
 * @throws IllegalArgumentException if this value isn't in the [ClosedRange]
 * starting with the specified [sourceRangeStart] and
 * ending with the specified [sourceRangeEndInclusive]
 */
@Suppress("NOTHING_TO_INLINE")
 inline fun Double.map(
    destinationRangeEndInclusive: Double,
    destinationRangeStart: Double = ValueRange.Normalized.start,
    sourceRangeStart: Double = ValueRange.Normalized.start,
    sourceRangeEndInclusive: Double = ValueRange.Normalized.endInclusive,
): Double = map(destinationRangeStart..destinationRangeEndInclusive, sourceRangeStart..sourceRangeEndInclusive)


/**
 * Returns this value with the specified [sourceRange] (default: `0.0..1.0`) mapped to the specified [destinationRange].
 *
 * @throws IllegalArgumentException if this value isn't in the specified [sourceRange]
 */
@Suppress("NOTHING_TO_INLINE")
 inline fun Double.map(
    destinationRange: ClosedRange<Int>,
    sourceRange: ClosedRange<Double> = ValueRange.Normalized,
): Int = map(destinationRange.start.toDouble()..destinationRange.endInclusive.toDouble(), sourceRange).roundToInt()

/**
 * Returns this value with the specified [sourceRangeStart] (default: `0.0`) and [sourceRangeEndInclusive] (default: `1.0`)
 * mapped to the [ClosedRange] starting with the specified [destinationRangeStart] (default: `0.0`) and ending with the
 * specified [destinationRangeEndInclusive].
 *
 * @throws IllegalArgumentException if this value isn't in the [ClosedRange]
 * starting with the specified [sourceRangeStart] and
 * ending with the specified [sourceRangeEndInclusive]
 */
@Suppress("NOTHING_TO_INLINE")
 inline fun Double.map(
    destinationRangeEndInclusive: Int,
    destinationRangeStart: Int = ValueRange.Normalized.min.toInt(),
    sourceRangeStart: Double = ValueRange.Normalized.min,
    sourceRangeEndInclusive: Double = ValueRange.Normalized.max,
): Int = map(destinationRangeStart..destinationRangeEndInclusive, sourceRangeStart..sourceRangeEndInclusive)
